#include "Subsystems/BlockBehaviors/SubsystemCrossbowBlockBehavior.h"
#include "Blocks/CrossbowBlock.h"
#include "Blocks/ArrowBlock.h"
#include "Managers/BlocksManager.h"
#include "Components/ComponentMiner.h"
#include "Components/ComponentCreature.h"
#include "Components/ComponentCreatureModel.h"
#include "Components/ComponentBody.h"
#include "Components/ComponentPlayer.h"
#include "Components/ComponentLevel.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemProjectiles.h"

namespace PocketSurvival
{

    static bool supportedArrowTypes[10] =
        {
            false, // None
            false, // WoodenArrow
            false, // StoneArrow
            false, // IronArrow
            false, // DiamondArrow
            false, // FireArrow
            true,  // IronBolt
            true,  // DiamondBolt
            true,  // ExplosiveBolt
            false  // CopperArrow
    };

    SubsystemCrossbowBlockBehavior::SubsystemCrossbowBlockBehavior()
    {
    }

    void SubsystemCrossbowBlockBehavior::setAimTime(ComponentMiner *componentMiner, double time)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        aimStartTimes[componentMiner] = time;
    }

    void SubsystemCrossbowBlockBehavior::removeAimTime(ComponentMiner *componentMiner)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        auto it = aimStartTimes.find(componentMiner);
        if (it != aimStartTimes.end())
        {
            aimStartTimes.erase(it);
        }
    }

    double SubsystemCrossbowBlockBehavior::getAimTime(ComponentMiner *componentMiner)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        auto it = aimStartTimes.find(componentMiner);
        if (it != aimStartTimes.end())
        {
            return it->second;
        }
        return -1;
    }

    void SubsystemCrossbowBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(15.0, 0.5) == true)
        {
            std::lock_guard<std::mutex> lk(m_mapMutex);
            bool needChange = false;
            double gameTime = m_timeOfDay->getGameTime();
            for (auto it = aimStartTimes.begin(); it != aimStartTimes.end(); ++it)
            {
                if (gameTime - it->second > 15.0)
                {
                    needChange = true;
                    break;
                }
            }
            if (needChange == true)
            {
                std::unordered_map<ComponentMiner *, double> aimTimeMap;
                for (auto it = aimStartTimes.begin(); it != aimStartTimes.end(); ++it)
                {
                    if (gameTime - it->second < 15.0)
                    {
                        aimTimeMap[it->first] = it->second;
                    }
                }
                aimStartTimes.swap(aimTimeMap);
            }
        }
    }

    bool SubsystemCrossbowBlockBehavior::onAim(const Ray3 &aim, ComponentMiner *componentMiner, AimState state)
    {
        IInventory *inventory = componentMiner->inventory;
        if (inventory != nullptr)
        {
            int32_t activeSlotIndex = inventory->getActiveSlotIndex();
            if (activeSlotIndex >= 0)
            {
                int32_t slotValue = inventory->getSlotValue(activeSlotIndex);
                int32_t slotCount = inventory->getSlotCount(activeSlotIndex);
                int32_t num = Terrain::ExtractContents(slotValue);
                int32_t data = Terrain::ExtractData(slotValue);
                if (num == CrossbowBlock::Index() && slotCount > 0)
                {
                    double value = getAimTime(componentMiner);
                    if (value < 0.0)
                    {
                        value = m_timeOfDay->getGameTime();
                        setAimTime(componentMiner, value);
                    }

                    int32_t draw = CrossbowBlock::GetDraw(data);
                    float num2 = (float)(m_timeOfDay->getGameTime() - value);
                    float num3 = (float)MathUtils::Remainder(m_timeOfDay->getGameTime(), 1000.0);

                    switch (state)
                    {
                    case AimState::InProgress:
                        if (num2 >= 10.0f)
                        {
                            return true;
                        }
                        break;
                    case AimState::Cancelled:
                        removeAimTime(componentMiner);
                        break;
                    case AimState::Completed:
                    {
                        ArrowType arrowType = CrossbowBlock::GetArrowType(data);
                        if (draw == 15 && arrowType != ArrowType::None)
                        {
                            Vector3 vector = componentMiner->componentCreature->componentCreatureModel->getEyePosition() + componentMiner->componentCreature->componentBody->getMatrix().getRight() * 0.3f - componentMiner->componentCreature->componentBody->getMatrix().getUp() * 0.2f;
                            Vector3 v2 = Vector3::Normalize(vector + aim.Direction * 10.0f - vector);

                            int32_t value2 = Terrain::MakeBlockValue(ArrowBlock::Index(), 0, ArrowBlock::SetArrowType(0, arrowType));
                            float s = 38.0f;

                            if (m_projectiles->fireProjectile(value2, vector, s * v2, FastVector3::Zero, componentMiner->componentCreature) != nullptr)
                            {
                                data = CrossbowBlock::SetArrowType(data, ArrowType::None);
                            }
                        }
                        inventory->removeSlotItems(activeSlotIndex, 1);
                        int32_t value3 = Terrain::MakeBlockValue(num, 0, CrossbowBlock::SetDraw(data, 0));
                        inventory->addSlotItems(activeSlotIndex, value3, 1);
                        componentMiner->damageActiveTool(1);
                        removeAimTime(componentMiner);
                        return true;
                    }
                    }
                }
            }
        }
        return false;
    }

    uint32_t SubsystemCrossbowBlockBehavior::getProcessInventoryItemCapacity(IInventory *inventory, uint32_t slotIndex, int32_t cellValue)
    {
        int32_t num = Terrain::ExtractContents(cellValue);
        ArrowType arrowType = ArrowBlock::GetArrowType(Terrain::ExtractData(cellValue));
        if (num == ArrowBlock::Index() && supportedArrowTypes[(uint8_t)arrowType] == true)
        {
            int32_t data = Terrain::ExtractData(inventory->getSlotValue(slotIndex));
            ArrowType arrowType2 = CrossbowBlock::GetArrowType(data);
            int32_t draw = CrossbowBlock::GetDraw(data);
            if (arrowType2 == ArrowType::None && draw == 15)
            {
                return 1;
            }
            return 0;
        }
        return 0;
    }

    void SubsystemCrossbowBlockBehavior::processInventoryItem(IInventory *inventory, uint32_t slotIndex, int32_t cellValue, uint32_t count, uint32_t processCount, int32_t &outProcessedValue, uint32_t &outProcessedCount)
    {
        if (processCount == 1)
        {
            ArrowType arrowType = ArrowBlock::GetArrowType(Terrain::ExtractData(cellValue));
            int32_t slotValue = inventory->getSlotValue(slotIndex);
            int32_t data = Terrain::ExtractData(slotValue);
            outProcessedValue = 0;
            outProcessedCount = 0;
            inventory->removeSlotItems(slotIndex, 1);
            inventory->addSlotItems(slotIndex, Terrain::MakeBlockValue(CrossbowBlock::Index(), 0, CrossbowBlock::SetArrowType(data, arrowType)), 1);
        }
        else
        {
            outProcessedValue = cellValue;
            outProcessedCount = count;
        }
    }

    void SubsystemCrossbowBlockBehavior::load(const nlohmann::json &json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_projectiles = GameSingleton::singletonPtr->getSingleton<SubsystemProjectiles>();
    }

    static const std::string subsystemName = "CrossbowBlockBehavior";
    const std::string &SubsystemCrossbowBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemCrossbowBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }


}